package mock

import (
	"bufio"
	"bytes"
	"errors"
	"io"
	"strconv"
)

// EncodeSimpleString builds a RESP simplestring
func EncodeSimpleString(s string) []byte {
	return []byte("+" + s + "\r\n")
}

// EncodeError builds a RESP error
func EncodeError(s string) []byte {
	return []byte("-" + s + "\r\n")
}

// EncodeBulkString builds a RESP bulkstring
func EncodeBulkString(s string) []byte {
	length := strconv.Itoa(len(s))
	return []byte("$" + length + "\r\n" + s + "\r\n")
}

// EncodeNullBulkString builds a RESP null bulkstring
func EncodeNullBulkString() []byte {
	return []byte("$-1\r\n")
}

// EncodeInteger builds a RESP integer
func EncodeInteger(v int64) []byte {
	s := strconv.FormatInt(v, 10)
	return []byte(":" + s + "\r\n")
}

// EncodeArray builds a RESP array
func EncodeArray(arr []interface{}) ([]byte, error) {
	s := strconv.Itoa(len(arr))
	buf := []byte("*" + s + "\r\n")
	for _, v := range arr {
		switch v := v.(type) {
		case string:
			buf = append(buf, EncodeBulkString(v)...)
		case []byte:
			buf = append(buf, EncodeBulkString(string(v))...)
		case int64:
			buf = append(buf, EncodeInteger(v)...)
		default:
			return nil, ErrInvalidProtocol
		}
	}
	return buf, nil
}

var (
	//ErrInvalidProtocol indicates a wrong protocol format
	ErrInvalidProtocol = errors.New("invalid protocol")
)

type Decoder struct {
	r *bufio.Reader
}

// NewDecoder creates a RESP coder
func NewDecoder(r io.Reader) *Decoder {
	return &Decoder{
		r: bufio.NewReader(r),
	}
}

func (c *Decoder) ReadCommand() ([][]byte, error) {
	p, err := c.r.Peek(1)
	if err != nil {
		return nil, err
	}
	if p[0] != '*' {
		return c.decodeInline()
	}

	argc, err := c.decodeArray()
	if err != nil {
		return nil, err
	}

	argv := make([][]byte, argc)
	for i := 0; i < argc; i++ {
		arg, err := c.decodeBulkString()
		if err != nil {
			return nil, err
		}
		argv[i] = arg
	}
	return argv, nil
}

// BulkString parses a RESP bulkstring
func (c *Decoder) decodeBulkString() ([]byte, error) {
	hdr, err := c.r.ReadSlice('\n')
	if err != nil {
		return nil, err
	}
	l := len(hdr)
	if l < len("$\r\n") {
		return nil, ErrInvalidProtocol
	}
	if hdr[l-2] != '\r' {
		return nil, ErrInvalidProtocol
	}
	if hdr[0] != '$' {
		return nil, ErrInvalidProtocol
	}

	if hdr[0] == '-' && len(hdr) == 2 && hdr[1] == '1' {
		// handle $-1 and $-1 null replies.
		return []byte{}, nil
	}

	remain, err := strconv.Atoi(string(hdr[1 : l-2]))
	if err != nil || remain < 0 {
		return nil, ErrInvalidProtocol
	}

	body := make([]byte, remain+2) //end with \r\n
	_, err = io.ReadFull(c.r, body)
	if err != nil {
		return nil, ErrInvalidProtocol
	}
	return body[:len(body)-2], nil
}

// Array parses a RESP array
func (c *Decoder) decodeArray() (int, error) {
	hdr, err := c.r.ReadSlice('\n')
	if err != nil {
		return -1, err
	}
	l := len(hdr)
	if l < len("*\r\n") {
		return -1, ErrInvalidProtocol
	}
	if hdr[l-2] != '\r' {
		return -1, ErrInvalidProtocol
	}
	if hdr[0] != '*' {
		return -1, ErrInvalidProtocol
	}
	remain, err := strconv.Atoi(string(hdr[1 : l-2]))
	if err != nil || remain < 0 {
		return -1, ErrInvalidProtocol
	}
	return remain, nil
}

func (c *Decoder) decodeInline() ([][]byte, error) {
	buf, err := c.r.ReadSlice('\n')
	if err != nil {
		return nil, err
	}

	return bytes.Fields(buf), nil
}
